package com.javasea.redis.utils;

import io.netty.util.internal.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.*;

@Component
public class RedisHashFieldCache {

	@Autowired
	private StringRedisTemplate redisTemplate;

    /**
     * 等待删除的集合,利用TreeSet的有序性,来排列即将过期的Field
     */
	private static final Set<Cache> WAIT_DEL_SET = Collections.synchronizedSet(new TreeSet<Cache>());

	/**
	 * 缓存等待删除的集合
	 * 存放到REDIS的一个哈希表里面,这是key值常量
	 */
	private static final String REDIS_HASH_KEY = "REDIS_HASH_FILED_CACHE";
	/**
	 * 缓存等待删除的集合
     * 存放到REDIS的一个哈希表里面,这是field值常量
	 */
	private static final String REDIS_HASH_FIELD;
	private static final Logger logger = LoggerFactory.getLogger(RedisHashFieldCache.class);
	static{
	    /**
	     * 用内网ip来区分每台机器的field值,当然你也可以改成其他的方式来区分本机方式
	     * 或者你觉得不需要用很多机器,只有一台机器,那你也可以不用哈希表,
	     * 你可以用一个普通的字符串Key就好啦.
	     * set或者list也是可以的.
	     * 我这里因为要区分很多机器,所以用内网ip来做FIELD常量
	     */
		REDIS_HASH_FIELD = "local_ip_" + RedisHashFieldCache.getLocalIp();
	}

	public static String getLocalIp(){
		try{
			Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces();
			while(en.hasMoreElements()){
				NetworkInterface ni = en.nextElement();
				Enumeration<InetAddress> addresses = ni.getInetAddresses();
				while(addresses.hasMoreElements()){
					InetAddress address = addresses.nextElement();
					if (!address.isLoopbackAddress() && !address.isLinkLocalAddress()
							&& address.isSiteLocalAddress()) {
						return address.getHostAddress().toString();
					}
				}
			}
		}catch (Exception e) {
		}
		return "127.0.0.1";
	}

	/**
	 * 初始化,每次系统启动的时候,从redis里面读取缓存队列,反序列化到WAIT_DEL_SET里面
	 */
	public static void init(){
		Object json = redisUtil.hget(REDIS_HASH_KEY, REDIS_HASH_FIELD);
		redisTemplate.opsForHash().get();
		List<Cache> list = null;
		try{
			if(!StringUtil.isEmpty(json) && !"[]".equals(json) && null != (list = JSON.parseArray(json, Cache.class))){
				WAIT_DEL_SET.addAll(list);
			}
		}catch (Exception e) {
			e.printStackTrace();
		}
		logger.info("RedisHashFieldCache init()!");
	}

	/**
	 * 设置一个redis里面的key和field,以及field的值,最后设置这个field的过期时间
	 * 过期时间是秒数,为了和redis里面expire设置key过期时间一致用秒数
	 * 所以这里统一使用了秒数来做为单位
	 */
	public static void put(String key, String field, String value, int seconds){
		if((key != null && key != "") || field != null && key != ""){
			return;
		}
		if(null == value){
			value = String.valueOf(System.currentTimeMillis());
		}
		Redis.hset(key, field, value);
		WAIT_DEL_SET.add(new Cache(seconds * 1000 + System.currentTimeMillis(), key, field));
		Redis.hset(REDIS_HASH_KEY, REDIS_HASH_FIELD, JSON.toJSONString(WAIT_DEL_SET));
	}

	/**
	 * 利用定时器Quartz(当然你也可以用其他的,Timer或者自己写一个线程,都可以)
	 * 利用定时功能,定时的来刷新这个缓存,并且从Reids中删除已经过期的field
	 */
	public static void flushCache(){
		Iterator<Cache> it = WAIT_DEL_SET.iterator();
		long now = System.currentTimeMillis();
		while(it.hasNext()){
			Cache c = it.next();
			if(c.expiredTime > now){
				break;
			}
			Redis.hdel(c.getKey(), c.getField());
			it.remove();
		}
		Redis.hset(REDIS_HASH_KEY, REDIS_HASH_FIELD, JSON.toJSONString(WAIT_DEL_SET));
	}

	/**
	 * 静态内部类Cache
	 * 这里面存放了过期时间,Reids里面的key,和field
	 * 重写了hashcode和equals方法,让他们只和key,field来判断唯一性
	 * 这里实现了Comparable方法,这个方法是为了配合TreeSet的有序集合来使用的
	 * compareTo方法中,将按照过期时间,顺序排列
	 * 这样当每次执行刷新方法的时候,迭代集合,总是先迭代最先必须要删除的Cache
	 * 如果当第n个Cache没有过期,那么后面的Cache都肯定没过期,所以就不需要迭代了
	 * 那么就不需要继续迭代后面的缓存了
	 */
	@SuppressWarnings("unused")
	private static class Cache implements Comparable<Cache>{
		private long expiredTime;
		private String key;
		private String field;

		public Cache() {}

		public Cache(long expiredTime, String key, String field) {
			super();
			this.expiredTime = expiredTime;
			this.key = key;
			this.field = field;
		}
		public long getExpiredTime() {
			return expiredTime;
		}
		public String getKey() {
			return key;
		}
		public String getField() {
			return field;
		}

		public void setExpiredTime(long expiredTime) {
			this.expiredTime = expiredTime;
		}
		public void setKey(String key) {
			this.key = key;
		}
		public void setField(String field) {
			this.field = field;
		}
		@Override
		public int compareTo(Cache o) {
			if(o == null){
				return -1;
			}
			if(o.getExpiredTime() == this.getExpiredTime()){
				return 0;
			}
			return this.getExpiredTime() > o.getExpiredTime() ? 1 : -1;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ((field == null) ? 0 : field.hashCode());
			result = prime * result + ((key == null) ? 0 : key.hashCode());
			return result;
		}
		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			Cache other = (Cache) obj;
			if (field == null) {
				if (other.field != null)
					return false;
			} else if (!field.equals(other.field))
				return false;
			if (key == null) {
				if (other.key != null)
					return false;
			} else if (!key.equals(other.key))
				return false;
			return true;
		}

		@Override
		public String toString() {
			return "Cache [expiredTime=" + expiredTime + ", key=" + key + ", field=" + field + "]";
		}
	}
}
